using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;

using System.Web;
using System.Security.Permissions;

using System.Threading;

using WMOSSExperts.Diagnostics;

namespace WMOSSExperts.Fields
{
    [CLSCompliant(false), SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
    public class AdvancedLookupField : SPFieldLookup
    {
        #region "Default constructors"
        public AdvancedLookupField(SPFieldCollection fields, String fieldName) : base(fields, fieldName) { this.InitField(fields); }

        public AdvancedLookupField(SPFieldCollection fields, String typeName, String displayName) : base(fields, typeName, displayName) { this.InitField(fields); }

        private void InitField(SPFieldCollection fields)
        {
            this.fields = fields;
            /*this.ShowInDisplayForm = true;
            this.ShowInEditForm = true;
            this.ShowInListSettings = true;
            this.ShowInNewForm = true;
            this.ShowInVersionHistory = true;
            this.ShowInViewForms = true;
            this.Required = true;*/
        }
        #endregion

        /// <summary>
        /// Point to the fields collection of the current sourceList
        /// </summary>
        private SPFieldCollection fields;

        private const String _kFieldNameReverseLookupGuid = "ReverseLookupGuid";
        private const String _kFieldNameRelatedLookupLinkFieldGuid = "RelatedLookupLinkFieldGuid";
        private const String _kFieldNameFilteredViewGuid = "FilteredViewGuid";
        private const String _kFieldNameAdditionalFields = "AdditionalFields";
        private const String _kSavingAdditionalFields = "SavingAdditionalFields";
        private const String _kPrefixToAdditionalField = "ALFREL_";


        #region "Event handlers ovveride"
        /// <summary>
        /// Set the related infrastructure after the new AdvancedLookup is added into a sourceList
        /// </summary>
        /// <param name="op"></param>
        public override void OnAdded(SPAddFieldOptions op)
        {
            base.OnAdded(op);

            //Check if the added column is not the reverse lookup; is not a related lookup and the parent list is defined
            if (this.ParentList != null & !this.CountRelated & String.IsNullOrEmpty(this.RelatedField))
            {
                //Set correct value to view this column in the file dialog
                FieldHelper.SetFieldAttribute("ShowInFileDlg", "TRUE", this);

                try
                {
                    SPSecurity.CodeToRunElevated setInfrastructure = new SPSecurity.CodeToRunElevated(SetRelatedInfrastructure);
                    SPSecurity.RunWithElevatedPrivileges(setInfrastructure);
                }
                catch (Exception ex)
                {
                    String message = String.Format("SetRelatedInfrastructure fails for source sourceList {0} and related Field id {1}; cause to the following error {2}", this.ParentList.Title, this.Id, ex);
                    TraceProvider.WriteTrace(TraceProvider.TraceSeverity.Exception, LogCategory.WMOSSExpertsCore, message);
                    throw new SPException(message, ex);

                }


            }

            //Call update to store custom properties value
            this.Update();
        }

        public override void Update()
        {
            //TODO Additional Fields
            //if (this.ParentList != null & !this.CountRelated & String.IsNullOrEmpty(this.RelatedField)) 
            //    this.SaveAdditionalFields(SPAddFieldOptions.Default);

            this.SetFieldProperties();
            base.Update();

            this.CleanNamedThreadData();
        }

        /// <summary>
        /// Remove the related infrastructure when the Advanced lookup is being deleting
        /// </summary>
        public override void OnDeleting()
        {
            //Check if this column is added in a sourceList or in the gallery
            //if the column is added in the gallery we can't create the related reverseLookupField
            if (this.ParentList == null) return;

            //Check if the added column is not the related column
            if (!this.CountRelated)
            {
                try
                {
                    SPSecurity.CodeToRunElevated removeInfrastructure = new SPSecurity.CodeToRunElevated(RemoveRelatedInfrastructure);
                    SPSecurity.RunWithElevatedPrivileges(removeInfrastructure);
                }
                catch (Exception ex)
                {
                    String message = String.Format("RemoveRelatedInfrastructure fails for source sourceList {0} and related Field id {1}; cause to the following error {2}", this.ParentList.Title, this.Id, ex);
                    TraceProvider.WriteTrace(TraceProvider.TraceSeverity.Exception, LogCategory.WMOSSExpertsCore, message);
                    throw new SPException(message, ex);
                }

                //TODO Additional Fields
                //SavingAdditionalFields.Clear();
                //this.SaveAdditionalFields(SPAddFieldOptions.Default);
            }

            base.OnDeleting();

        }
        #endregion
        
        #region "Remove related infrastructure"
        /// <summary>
        /// Remove the related infrastructure
        /// </summary>
        private void RemoveRelatedInfrastructure()
        {
            if (this.LookupWebId == Guid.Empty) return;

            using(Logger Log = new Logger("AdvancedLookupField.RemoveRelatedInfrastructure"))
            {

                using (SPWeb web = SPContext.Current.Site.OpenWeb(this.LookupWebId))
                {
                    SPList targetedList = web.Lists[new Guid(this.LookupList)];
                    this.RemoveRelatedLookupLinkColumn(targetedList);
                    this.RemoveReverseLookup(targetedList);
                    targetedList.Update();
                    this.RemoveFilteredView(targetedList);
                }
            }

        }


        /// <summary>
        /// Remove the reverse lookup
        /// </summary>
        /// <param name="targetedList"></param>
        private void RemoveReverseLookup(SPList targetedList)
        {
            AdvancedLookupField field = (AdvancedLookupField)FieldHelper.GetExistingField(targetedList, this.ReverseLookupGuid);
            if (field == null) return;

            field.Delete();
        }

        /// <summary>
        /// Remove values in the related lookup links reverseLookupField. If this column is empty it will also be deleted
        /// </summary>
        /// <param name="targetedList"></param>
        private void RemoveRelatedLookupLinkColumn(SPList targetedList)
        {
            RelatedLookupLinksField field = (RelatedLookupLinksField)FieldHelper.GetExistingField(targetedList, this.RelatedLookupLinkFieldGuid);
            if (field == null) return;

            field.RemoveLookupLink(this.ReverseLookupGuid);
            if (field.RelatedLinksCount == 0) field.Delete();
            else field.Update();
        }

        /// <summary>
        /// Remove Filtered View
        /// </summary>
        /// <param name="targetedList"></param>
        private void RemoveFilteredView(SPList targetedList)
        {
            SPViewCollection views = this.ParentList.Views;
            try
            {
                SPView view = this.ParentList.Views[this.FilteredViewGuid];
                if (view != null) this.ParentList.Views.Delete(view.ID);
            }
            catch { }
        }

        #endregion

        #region "Create the related infrastructure"

        /// <summary>
        /// Set the related infrastructure
        /// </summary>
        private void SetRelatedInfrastructure()
        {
            if (this.LookupWebId == Guid.Empty) return;

            using (Logger Log = new Logger("AdvancedLookupField.SetRelatedInfrastructure"))
            {

                using (SPWeb web = SPContext.Current.Site.OpenWeb(this.LookupWebId))
                {
                    SPList targetedList = web.Lists[new Guid(this.LookupList)];

                    //Create the reverse lookup and store the data in the custom property reverseLookupField
                    Guid reverseLookupId = this.CreateReverseLookup(targetedList);
                    this.ReverseLookupGuid = reverseLookupId;

                    //Create the filtered view
                    Guid viewId = this.CreateFilteredView(targetedList);
                    this.FilteredViewGuid = viewId;

                    Guid relatedLookupId = this.CreateRelatedLookupLinkColumn(targetedList, reverseLookupId, viewId);
                    this.RelatedLookupLinkFieldGuid = relatedLookupId;

                    targetedList.Update();
                    SPContext.Current.Site.WebApplication.Uncache();


                }
            }

        }
                
        /// <summary>
        /// Create the reverse lookup reverseLookupField in the targeted sourceList
        /// </summary>
        /// <param name="targetedList"></param>
        /// <returns></returns>
        private Guid CreateReverseLookup(SPList targetedList)
        {
            
            String columnName = "Reverse " + this.ParentList.ID.ToString();
            String internalName = columnName.Replace(" ","_x0020_");
            internalName = internalName.Replace("-", "_x002d_");
            internalName = internalName.Substring(0,32);

            AdvancedLookupField field = null;
            try
            {
                field = (AdvancedLookupField)targetedList.Fields.GetFieldByInternalName(internalName);
            }
            catch { }

            if (field != null) return field.Id;
            
            field = (AdvancedLookupField)targetedList.Fields.CreateNewField(this.TypeAsString, columnName);
            field.LookupWebId = this.ParentList.ParentWeb.ID;
            field.LookupList = this.ParentList.ID.ToString();
            field.LookupField = this.InternalName;
            field.CountRelated = true;
            field.ShowInDisplayForm = true;
            FieldHelper.SetFieldAttribute("AuthoringInfo", "(Automatically created from " + this.ParentList.Title + ")", field);
            internalName = targetedList.Fields.Add(field);

            SPField createdField = targetedList.Fields.GetFieldByInternalName(internalName);
            if (createdField == null) return Guid.Empty;

            createdField.Title = "Related " + this.ParentList.Title;
            createdField.Update();
            return createdField.Id;
        }
                
        /// <summary>
        /// Create the filtered View in the current sourceList to allow filter this sourceList from the targeted sourceList
        /// </summary>
        /// <param name="targetedList"></param>
        private Guid CreateFilteredView(SPList targetedList)
        {
            SPViewCollection views = this.ParentList.Views;
            String viewName = "ViewBy" + targetedList.Title;

            SPView view = null;
            try
            {
                 view = this.ParentList.Views[viewName];
                if (view != null) return view.ID;
            }
            catch{}
            
            System.Collections.Specialized.StringCollection viewFields = new System.Collections.Specialized.StringCollection();

            //Make a copy of the reverseLookupField in the default view
            foreach (string viewField in this.ParentList.DefaultView.ViewFields)
            {
                if (String.Compare(viewField, this.InternalName, true) != 0) viewFields.Add(viewField);
            }

            string query = String.Format("<GroupBy Collapse=\"FALSE\"><FieldRef Name='{0}' /></GroupBy>",this.InternalName);
            query += "<OrderBy><FieldRef Name=\"ID\"></FieldRef></OrderBy>";
			query += String.Format("<Where><Eq><FieldRef Name='{0}' LookupId='TRUE'/><Value Type=''><GetVar Scope='Request' Name='Value'/></Value></Eq></Where>", this.InternalName);


            view = views.Add(viewName, viewFields, query,100, true, false, SPViewCollection.SPViewType.Html, false);
            view.Hidden = true;
            view.Scope = SPViewScope.RecursiveAll;
            view.Update();
            return view.ID;

        }

        /// <summary>
        /// Create the related lookup links reverseLookupField
        /// </summary>
        /// <param name="targetedList"></param>
        /// <param name="reverseFieldId"></param>
        /// <param name="filteredViewId"></param>
        /// <returns></returns>
        private Guid CreateRelatedLookupLinkColumn(SPList targetedList, Guid reverseFieldId, Guid filteredViewId)
        {
            String columnName = "RelatedLists " + targetedList.ID.ToString();
            String internalName = columnName.Replace(" ", "_x0020_");
            internalName = internalName.Replace("-", "_x002d_");
            internalName = internalName.Substring(0, 32);

            RelatedLookupLinksField field = null;
            try
            {
                field = (RelatedLookupLinksField)targetedList.Fields.GetFieldByInternalName(internalName);
            }
            catch { }

            if (field != null)
            {
                field.AddLookupLink(reverseFieldId, filteredViewId,this.ParentList.Title, true);
                field.Update();
                return field.Id;
            }
            else
            {
                field = (RelatedLookupLinksField)targetedList.Fields.CreateNewField("RelatedLookupLinksField", columnName);
                field.ShowInDisplayForm = true;
                field.ShowInViewForms = false;
                


                FieldHelper.SetFieldAttribute("AuthoringInfo", "(Automatically created to display child lists )", field);
                internalName = targetedList.Fields.Add(field);

                RelatedLookupLinksField createdField = (RelatedLookupLinksField)targetedList.Fields.GetFieldByInternalName(internalName);
                if (createdField == null) return Guid.Empty;

                createdField.AddLookupLink(reverseFieldId, filteredViewId, this.ParentList.Title, true);
                createdField.Title = "View Related Lists";
                createdField.Update();

                return createdField.Id;
                
            }
        }

        #endregion

        #region "Field properties"

        /// <summary>
        /// The filtered view Guid created in this current sourceList
        /// </summary>
        private Guid filteredViewGuid = Guid.Empty;

        /// <summary>
        /// Gets or sets the filtered view Guid created to filter data from the parent sourceList
        /// </summary>
        public Guid FilteredViewGuid
        {
            get
            {
                if (this.filteredViewGuid.Equals(Guid.Empty))
                {
                    string viewGuid = FieldHelper.GetCurrentPropertyValue(this, _kFieldNameFilteredViewGuid);
                    if (!String.IsNullOrEmpty(viewGuid))
                    {
                        Guid value = ParserHelper.ParseStringToGuid(viewGuid);
                        if (value.Equals(Guid.Empty))
                        {
                            this.SetFilteredViewGuidValue(value);
                        }
                        this.filteredViewGuid = value;
                    }

                }
                return this.filteredViewGuid;
            }
            set
            {
                this.SetFilteredViewGuidValue(value);
            }
        }

        /// <summary>
        /// Set the Filtered View Guid value in the local variable and in the thread data slot
        /// </summary>
        /// <param name="value"></param>
        private void SetFilteredViewGuidValue(Guid value)
        {
            this.filteredViewGuid = value;
            FieldHelper.SetNamedThreadData(_kFieldNameFilteredViewGuid, value.ToString());
        }       
        
        /// <summary>
        /// The reverse lookup Guid value created in the targeted sourceList of this lookup
        /// </summary>
        private Guid reverseLookupGuid = Guid.Empty;
        /// <summary>
        /// Gets or sets the reverse lookup reverseLookupField Guid created in the targeted sourceList of this lookup 
        /// </summary>
        public Guid ReverseLookupGuid
        {
            get
            {
                if (this.reverseLookupGuid.Equals(Guid.Empty))
                {
                    string fieldGuid = FieldHelper.GetCurrentPropertyValue(this, _kFieldNameReverseLookupGuid);
                    if (!String.IsNullOrEmpty(fieldGuid))
                    {
                        Guid value = ParserHelper.ParseStringToGuid(fieldGuid);
                        if (value.Equals(Guid.Empty))
                        {
                            this.SetReverseLookupGuidValue(value);
                        }
                        this.reverseLookupGuid = value;
                    }

                }
                return this.reverseLookupGuid;
            }
            set
            {
                this.SetReverseLookupGuidValue(value);
            }
        }

        /// <summary>
        /// Set the reverse lookup Guid value in the local variable and in the thread data slot
        /// </summary>
        /// <param name="value"></param>
        private void SetReverseLookupGuidValue(Guid value)
        {
            this.reverseLookupGuid = value;
            FieldHelper.SetNamedThreadData(_kFieldNameReverseLookupGuid, value.ToString());
        }


        /// <summary>
        /// The related lookup Links reverseLookupField Guid created in the targeted sourceList of this lookup
        /// </summary>
        private Guid relatedLookupLinkFieldGuid = Guid.Empty;
        
        /// <summary>
        /// Gets or sets the related lookup Links reverseLookupField Guid created in the targeted sourceList of this lookup 
        /// </summary>
        public Guid RelatedLookupLinkFieldGuid
        {
            get
            {
                if (this.relatedLookupLinkFieldGuid.Equals(Guid.Empty))
                {
                    string fieldGuid = FieldHelper.GetCurrentPropertyValue(this, _kFieldNameRelatedLookupLinkFieldGuid);
                    if (!String.IsNullOrEmpty(fieldGuid))
                    {
                        Guid value = ParserHelper.ParseStringToGuid(fieldGuid);
                        if (value.Equals(Guid.Empty))
                        {
                            this.SetRelatedLookupLinkGuidValue(value);
                        }
                        this.relatedLookupLinkFieldGuid = value;
                    }

                }
                return this.relatedLookupLinkFieldGuid;
            }
            set
            {
                this.SetRelatedLookupLinkGuidValue(value);
            }
        }

        /// <summary>
        /// Set the reverse lookup Guid value in the local variable and in the thread data slot
        /// </summary>
        /// <param name="value"></param>
        private void SetRelatedLookupLinkGuidValue(Guid value)
        {
            this.relatedLookupLinkFieldGuid = value;
            FieldHelper.SetNamedThreadData(_kFieldNameRelatedLookupLinkFieldGuid, value.ToString());
        }

        /// <summary>
        /// The additional fields
        /// </summary>
        private String _additionalFields;

        /// <summary>
        /// Gets or sets the additional fields
        /// </summary>
        private String AdditionalFields
        {
            get
            {
                if (String.IsNullOrEmpty(_additionalFields))
                {
                    _additionalFields = (String)FieldHelper.GetCurrentPropertyValue(this, _kFieldNameAdditionalFields);
                }
                return _additionalFields;
            }
            set
            {
                _additionalFields = value;
                FieldHelper.SetNamedThreadData(_kFieldNameAdditionalFields, value);
            }
        }

        
        #endregion

        #region "Additional fields management"

        /*
        [ThreadStatic]
        private static List<String> SavingAdditionalFields = new List<string>();

        /// <summary>
        /// Save the list of additional fields in the field custom property
        /// </summary>
        private void SaveAdditionalFields(SPAddFieldOptions op)
        {
            if (SavingAdditionalFields == null) SavingAdditionalFields = new List<string>();

            List<String> additionalFields = this.GetCurrentAdditionalFields();
            String[] additionalFieldsTab = additionalFields.ToArray();
            //remove fields
            foreach (String fieldName in additionalFieldsTab)
            {
                //The field must be keeped
                if (SavingAdditionalFields.Contains(fieldName)) continue;

                //remove the field
                this.RemoveAdditionalField(fieldName);
                additionalFields.Remove(fieldName);
            }

            //add new fields in the list
            foreach (String fieldName in SavingAdditionalFields)
            {
                if (additionalFields.Contains(fieldName)) continue;
                additionalFields.Add(fieldName);
            }

            //create additional field in the list
            this.AdditionalFields = String.Empty;
            foreach(String fieldName in additionalFields)
            {
                this.CreateAdditionalField(fieldName, op);
                this.AdditionalFields += fieldName + ";";
            }
            
        }

        /// <summary>
        /// Create the additional field
        /// </summary>
        /// <param name="fieldName"></param>
        private void CreateAdditionalField(String fieldName, SPAddFieldOptions op)
        {
            try
            {
                SPField field = this.ParentList.Fields.GetFieldByInternalName(_kPrefixToAdditionalField + fieldName);
                if (field != null) return;
            }
            catch { }

            SPFieldLookup additionalField = (SPFieldLookup)fields.CreateNewField(SPFieldType.Lookup.ToString(), _kPrefixToAdditionalField + fieldName);
            additionalField.LookupWebId = this.LookupWebId;
            additionalField.LookupList = this.LookupList;
            FieldHelper.SetFieldAttribute("ShowField", fieldName, additionalField);
            FieldHelper.SetFieldAttribute("FieldRef", fieldName, additionalField);

            additionalField.ShowInDisplayForm = true;
            String internalName = fields.Add(additionalField);

            SPField createdField = fields.GetFieldByInternalName(internalName);
            if (createdField != null)
            {
                createdField.Title = fieldName;
                createdField.Update();
            }
                        
        }

        /// <summary>
        /// Remove the additional field
        /// </summary>
        /// <param name="fieldName"></param>
        private void RemoveAdditionalField(String fieldName)
        {
            try
            {
                AdvancedLookupField field = (this.ParentList.Fields.GetFieldByInternalName(_kPrefixToAdditionalField + fieldName) as AdvancedLookupField);
                if (field != null) field.Delete();
            }
            catch { }

        }

        /// <summary>
        /// Gets Additional fields
        /// </summary>
        /// <returns></returns>
        public List<String> GetCurrentAdditionalFields()
        {
            List<String> additionalFields = new List<String>();
            if (!String.IsNullOrEmpty(this.AdditionalFields))
            {
                String[] fields = this.AdditionalFields.Split(new String[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
                foreach (String fieldName in fields)
                {
                    additionalFields.Add(fieldName);
                }
            }
            return additionalFields;
        }

        /// <summary>
        /// Set additional field
        /// </summary>
        /// <param name="internalFieldName"></param>
        public void SetAdditionalField(String internalFieldName)
        {
            if (String.IsNullOrEmpty(internalFieldName)) return;

            if (SavingAdditionalFields != null && !SavingAdditionalFields.Contains(internalFieldName))
                SavingAdditionalFields.Add(internalFieldName);

        }
        */
        #endregion

       

        #region "Manage internal fields attributes & property schema fields"

        private void SetFieldProperties()
        {
            base.SetCustomProperty(_kFieldNameReverseLookupGuid, FieldHelper.GetCurrentPropertyValue(this,_kFieldNameReverseLookupGuid));
            base.SetCustomProperty(_kFieldNameRelatedLookupLinkFieldGuid, FieldHelper.GetCurrentPropertyValue(this,_kFieldNameRelatedLookupLinkFieldGuid));
            base.SetCustomProperty(_kFieldNameFilteredViewGuid, FieldHelper.GetCurrentPropertyValue(this,_kFieldNameFilteredViewGuid));
            //base.SetCustomProperty(_kFieldNameAdditionalFields, FieldHelper.GetCurrentPropertyValue(this, _kFieldNameAdditionalFields));
        }

        private void CleanNamedThreadData()
        {
            Thread.FreeNamedDataSlot(_kFieldNameReverseLookupGuid);
            Thread.FreeNamedDataSlot(_kFieldNameRelatedLookupLinkFieldGuid);
            Thread.FreeNamedDataSlot(_kFieldNameFilteredViewGuid);
            //Thread.FreeNamedDataSlot(_kFieldNameAdditionalFields);
        }

        #endregion

    }
}
